#include <cserver/restapi.h>
#include <cserver/module.h>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <pqxx/transaction.hxx>

#include <sstream>

namespace csrv{ namespace web_v2{
	namespace {
		struct SqlRegister{
			SqlRegister(){
				{
				auto& sql = GetApiSqlTable()["v2_sql_login"];
				sql.sql_name = "v2_sql_login";
				sql.sql = "select a.id, a.account, a.name, a.orgid, b.name orgname from t_user2 a, t_organization b "
					" where a.orgid = b.id and a.account=$1 and a.password = $2 and b.prefix = $3";
				}
				{
				auto& sql = GetApiSqlTable()["v2_sql_get_modules_by_userid"];
				sql.sql_name = "v2_sql_get_modules_by_userid";
				sql.sql = "select a.role, b.path from t_user2_modules a, t_modules b where a.userid=$1 and a.moduleid = b.id";
				}
				{
				auto& sql = GetApiSqlTable()["v2_sql_get_uiid_by_userid"];
				sql.sql_name = "v2_sql_get_uiid_by_userid";
				sql.sql = "select a.name, a.displayname, a.description , a.id from t_module_ui a, t_user2_modules b where b.userid=$1 and b.moduleid = a.moduleid";
				}

			}
		};
		SqlRegister k_sql_register;
	}

	static const char* K_this_command= "api/v2/account/login";
	std::string makeLoginResponse(const std::string& account, bool success, const std::string& message, const std::string& sessionid, 
			const std::string& name, const std::string& orgid, const std::string& uilist){
		std::stringstream sstr;
		sstr << boost::format(R"({
"func":"login",
"args":{"account":%1%},
"ret":{"success":%2%, "message":%3%,
"value":{"sessionid":%4%, 
"name": %5%,
"orgid": %6%,
"ui": %7%,
"expires":""}}
})") 
			% escape_string(account) % std::string(success ? "true" : "false") % escape_string(message) % escape_string(sessionid) % escape_string(name) % orgid % uilist;

			return sstr.str();
	}

	void login(Request& req, pqxx::connection& dbconn, SessionContext& sctx, const std::string& /*cmd*/ ){
		auto query = parseQuery(req.getParam("QUERY_STRING"));
		auto& callback = query["callback"];
		auto sessionid = req.getCookie("sessionid");
		{
			std::lock_guard<std::mutex> lock(sctx.mutex);
			if(sctx.sessionTable.erase(sessionid) !=0)
			{
				pqxx::work w(dbconn);
				w.prepared("delete_sessionid")(sessionid).exec();
				w.commit();
			}
		}

		StreamGroup sg(req, 4096);

		boost::property_tree::ptree ptree;
		read_json(sg.fin, ptree);

		auto account = ptree.get<std::string>("account", "");
		auto itr = std::find(account.begin(), account.end(), '\\');
		std::string org_prefix(account.begin(), itr);
		if (itr != account.end()) ++itr;
		account = std::string(itr, account.end());
		if (account.empty()){
			account = org_prefix;
			org_prefix = "mq"; // Ma Qun 
		}

		auto password = ptree.get<std::string>("password", "");
		if (!account.empty() && !password.empty() && !org_prefix.empty()){
			std::string hashed_text = hash_password(account, password);

			pqxx::work w(dbconn);
			auto result = w.prepared("v2_sql_login")(account)(hashed_text)(org_prefix).exec();
			if (!result.empty()){
				auto row = result.begin();
				auto field = row->begin();
				SessionInfo sinfo;
				sinfo.userid = field->c_str(); ++field;
				sinfo.account = field->c_str(); ++field;
				sinfo.displayName = field->c_str(); ++field;
				sinfo.orgid = field->c_str(); ++field;
				sinfo.orgname = field->c_str();
				result.clear();
				// load roles
				{
					auto records = w.prepared("v2_sql_get_modules_by_userid")(sinfo.userid).exec();
					for(auto r : records){
						auto field = r.begin();
						std::string role = field->c_str(); 
						++field;
						sinfo.module_role[field->c_str()] = role;
					}
				}

				auto result = w.prepared("new_sessionid").exec();
				std::string sessionid = result.begin()->begin()->c_str();
				w.prepared("store_sessionid")(sinfo.userid)(sessionid).exec();
				std::string uilist = result2json(w.prepared("v2_sql_get_uiid_by_userid")(sinfo.userid).exec(), 3);
				w.commit();

				response_header(sg.fout, 200, false);
				sg.fout << "Set-Cookie: sessionid=" << sessionid << "; path=/ \r\n\r\n";
				if (!callback.empty()) sg.fout << callback << "(";
				sg.fout <<  makeLoginResponse(account, true, "", sessionid, sinfo.displayName, sinfo.orgid, uilist);
				if (!callback.empty()) sg.fout << ")";

				std::lock_guard<std::mutex> lock(sctx.mutex);
				sctx.sessionTable[sessionid] = std::move(sinfo);

				return;
			}
		}
		response_header(sg.fout, 401);
		if (!callback.empty()) sg.fout << callback << "(";
		sg.fout <<  makeLoginResponse(account, false, "Failed to login", "", "", "null", "null");
		if (!callback.empty()) sg.fout << ")";
	}

}
static CommandAddHelper k_registerHandler("POST", web_v2::K_this_command, &web_v2::login);
}

